#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Task.hpp"

using namespace std;

enum
{
    NAME_SIZE = 64
};

// 子进程基本信息类
class ProcINfo
{

public:
    ProcINfo(pid_t pid = pid_t(), int wfd = int())
        : _pid(pid), _wfd(wfd), _num(_cnt++)
    {
        char buff[NAME_SIZE] = {0};
        snprintf(buff, NAME_SIZE, "Process %d | pid:wfd [%d:%d]", _num, _pid, _wfd);
        _name = string(buff);
    }

    pid_t _pid;
    int _wfd;
    int _num;
    string _name;
    static int _cnt;
};

int ProcINfo::_cnt = 0;

// 进程控制类
class ProcCtrl
{
public:
    ProcCtrl(int num = 3, mode_t mode = 0666)
        : _num(num), _mode(mode)
    {
        // 根据 _num 创建命名管道及子进程
        CreatPipeAndProc();
    }

    ~ProcCtrl()
    {
        waitProc();
    }

    // 创建管道及进程
    void CreatPipeAndProc()
    {
        // 因为是继承的，所以也要注意写端重复继承问题
        vector<int> fds;
        for (int i = 0; i < _num; i++)
        {
            // 步骤：创建管道，存入 _vst
            char pipeNameBUff[NAME_SIZE]; // 管道名缓冲区
            snprintf(pipeNameBUff, NAME_SIZE, "./fifo-%d", i);

            int ret = mkfifo(pipeNameBUff, _mode);
            assert(ret != -1);
            (void)ret;

            _vst.push_back(string(pipeNameBUff));

            // 创建子进程，让子进程以只读的方式打开管道文件
            pid_t id = fork();
            if (id == 0)
            {
                // 子进程内
                // 先关闭不必要的写端
                for (auto e : fds)
                    close(e);

                // 打开管道文件，并进入任务等待默认（读端阻塞）
                int rfd = open(_vst[i].c_str(), O_RDONLY);
                assert(rfd != -1);
                (void)rfd;

                waitCommand(rfd);

                close(rfd); // 关闭读端
                exit(0);
            }

            // 父进程以写打开管道，保存 fd 信息
            int wfd = open(_vst[i].c_str(), O_WRONLY);
            assert(wfd != -1);
            (void)wfd;

            // 注册子进程信息
            _vpt.push_back(ProcINfo(id, wfd));
            fds.push_back(wfd);
        }
    }

    // 子进程等待任务派发
    void waitCommand(int rfd)
    {
        while (true)
        {
            char buff[NAME_SIZE] = {0};
            int n = read(rfd, buff, sizeof(buff) - 1);

            buff[n] = '\0';

            if (n > 0)
            {
                Task().Execute(string(buff));
            }
            else if (n == 0)
            {
                cerr << "读端读取到 0，写端已关闭，读端也即将关闭" << endl;
                break;
            }
            else
            {
                cerr << "子进程读取异常！" << endl;
                break;
            }
        }
    }

    // 展示可选进程
    void showProc()
    {
        cout << "目前可用进程有：[";
        int i = 0;
        for (i = 0; i < _num - 1; i++)
            cout << i << "|";
        cout << i << "]" << endl;
    }

    // 下达任务给子进程
    void ctrlProc()
    {
        while (true)
        {
            cout << "==========================" << endl;
            int n = 0;
            do
            {
                showProc();
                cout << "请选择子进程:> ";
                cin >> n;
            } while (n < 0 || n >= _num);

            Task().showTask();
            string taskName;
            cout << "请选择任务:> ";
            cin >> taskName;

            if (taskName == "退出")
                break;

            // 将信息通过命名管道写给子进程
            cout << "选择进程 ->" << _vpt[n]._name << " 执行 " << taskName << " 任务" << endl;
            write(_vpt[n]._wfd, taskName.c_str(), taskName.size());
            sleep(1);
        }
    }

    // 关闭写端、删除文件、等待子进程退出
    void waitProc()
    {
        for (int i = 0; i < _num; i++)
        {
            close(_vpt[i]._wfd);               // 关闭写端
            unlink(_vst[i].c_str());           // 关闭管道文件
            waitpid(_vpt[i]._pid, nullptr, 0); // 等待子进程
        }

        cout << "所有子进程已回收" << endl;
    }

private:
    vector<ProcINfo> _vpt; // 子进程信息表
    vector<string> _vst;   // 命名管道信息表
    int _num;              // 子进程数/命名管道数
    mode_t _mode;          // 命名管道文件的权限
};

int main()
{
    ProcCtrl p1;
    p1.ctrlProc();
    return 0;
}